﻿using System.Text;

namespace GameOfLife
{
	/// <summary>
	/// A simulator for the John Conway`s game of life.
	/// </summary>
	public class GameOfLifeSimulator
	{
		private int boardSize;
		private int foodEaten = 0;

		/// <summary>
		/// Gets the current state of the board in the game of life.
		/// </summary>
		public CellState[,] Board { get; private set; }

		/// <summary>
		/// Gets the quality of the current generation.
		/// </summary>
		public int CurrentGenerationQuality { get; private set;}

		/// <summary>
		/// Initializes a new game of life simulator on a empty board with the given size.
		/// </summary>
		/// <param name="boardSize"></param>
		public GameOfLifeSimulator(int boardSize)
		{
			Board = new CellState[boardSize, boardSize];
			this.boardSize = boardSize;
		}

		/// <summary>
		/// Simulates the game of life. You may optionally specify the numbers of turns to be simulated. If omitted, a single turn will be simmed.
		/// </summary>
		/// <param name="turns">The number of turns to simulate</param>
		public void Simulate(int turns = 1)
		{
			for (int i = 0; i < turns; i++)
			{
				SimulateSingleTurn();
			}
		}

		private void SimulateSingleTurn()
		{
			CellState[,] nextBoard = new CellState[boardSize, boardSize];
			int aliveCellsCount = 0;

			for (int i = 0; i < boardSize; i++)
			{
				for (int j = 0; j < boardSize; j++)
				{
					int aliveNeighbours = CountAliveNeighbours(i, j);
					if (Board[i, j] == CellState.Alive && (aliveNeighbours == 2 || aliveNeighbours == 3))
					{
						nextBoard[i, j] = CellState.Alive;
						aliveCellsCount++;
					}
					else if (Board[i, j] != CellState.Alive && aliveNeighbours == 3)
					{
						if (Board[i, j] == CellState.Food)
							foodEaten++;
						nextBoard[i, j] = CellState.Alive;
						aliveCellsCount++;
					}
					else
					{
						nextBoard[i, j] = CellState.Dead;
					}
				}
			}

			Board = nextBoard;
			if (aliveCellsCount != 0)
				CurrentGenerationQuality = aliveCellsCount + foodEaten;
			else
				CurrentGenerationQuality = 0;
		}

		private int CountAliveNeighbours(int row, int col)
		{
			int alive = 0;
			//Starting from the cell that has coordinates (row - 1, col - 1) iterate over all the neighbours of the our cell
			for (int i = row - 1; i < row + 2; i++)
			{
				for (int j = col - 1; j < col + 2; j++)
				{
					if (i == row && j == col)
						continue;
					if (GetState(i, j) == CellState.Alive)
						alive++;
				}
			}

			return alive;
		}

		private CellState GetState(int row, int col)
		{
			if (row >= 0 &&
				row < boardSize &&
				col >= 0 &&
				col < boardSize)
			{
				return Board[row, col];
			}

			return CellState.NoneExistent;
		}

		/// <summary>
		/// Returns a string representing the state of the current board.
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			StringBuilder builder = new StringBuilder();
			for (int i = 0; i < boardSize; i++)
			{
				for (int j = 0; j < boardSize; j++)
				{
					builder.Append(GetSymbol(Board[i, j]));
				}
				builder.AppendLine();
			}

			return builder.ToString();
		}

		static char GetSymbol(CellState state)
		{
			if (state == CellState.Alive)
				return '\u25A0';
			else if (state == CellState.Dead)
				return '\u25A1';
			else if (state == CellState.Food)
				return 'Ѽ';
			else
				return '!';
		}

	}

	/// <summary>
	/// Represents the state of a single cell.
	/// </summary>
	public enum CellState
	{
		/// <summary>
		/// Represents a dead cell.
		/// </summary>
		Dead = 'o',
		/// <summary>
		/// Represents an alive cell.
		/// </summary>
		Alive = '+',
		/// <summary>
		/// Represents an empty cell that has food
		/// </summary>
		Food = 'F',
		/// <summary>
		/// Special value used for denoting cells that are outside the board
		/// </summary>
		NoneExistent = 'N'
	}
}
